import exceptions

from plex import *
from utils import run_scanner

class NaughtyNaughty(exceptions.Exception):
    pass

class MyScanner(Scanner):
    bracket_nesting_level = 0
    indentation_stack = None
    indentation_char = None

    def current_level(self):
        return self.indentation_stack[-1]

    def open_bracket_action(self, text):
        self.bracket_nesting_level = self.bracket_nesting_level + 1
        return text

    def close_bracket_action(self, text):
        self.bracket_nesting_level = self.bracket_nesting_level - 1
        return text

    def newline_action(self, text):
        if self.bracket_nesting_level == 0:
            self.begin('indent')
            self.produce('newline', '')

    def indentation_action(self, text):
        self.begin('')
        # Check that tabs and spaces are being used consistently.
        if text:
            c = text[0]
            if self.indentation_char is None:
                self.indentation_char = c
            else:
                if self.indentation_char <> c:
                    raise NaughtyNaughty("Mixed up tabs and spaces!")

        # Figure out how many indents/dedents to do
        current_level = self.current_level()
        new_level = len(text)
        if new_level == current_level:
            return
        elif new_level > current_level:
            self.indentation_stack.append(new_level)
            self.produce('INDENT', '')
        else:
            while new_level < self.current_level():
                del self.indentation_stack[-1]
                self.produce('DEDENT', '')

            if new_level <> self.current_level():
                raise NaughtyNaughty("Indentation booboo!")

    def eof(self):
        while len(self.indentation_stack) > 1:
            self.produce('DEDENT', '')
            self.indentation_stack.pop()

    letter = Range("AZaz") | Any("_")
    digit = Range("09")
    hexdigit = Range("09AFaf")
    indentation = Rep(Str(" ")) | Rep(Str("\t"))

    name = letter + Rep(letter | digit)
    number = Rep1(digit) | (Str("0x") + Rep1(hexdigit))
    sq_string = (
        Str("'") + 
        Rep(AnyBut("\\\n'") | (Str("\\") + AnyChar)) + 
        Str("'"))
    dq_string = (
        Str('"') + 
        Rep(AnyBut('\\\n"') | (Str("\\") + AnyChar)) + 
        Str('"'))
    non_dq = AnyBut('"') | (Str('\\') + AnyChar)
    tq_string = (
        Str('"""') +
        Rep(
            non_dq |
            (Str('"') + non_dq) |
            (Str('""') + non_dq)) + Str('"""'))
    stringlit = sq_string | dq_string | tq_string
    bra = Any("([{")
    ket = Any(")]}")
    punct = Any(":,;+-*/|&<>=.%`~^")
    diphthong = Str("==", "<>", "!=", "<=", "<<", ">>", "**")
    spaces = Rep1(Any(" \t"))
    comment = Str("#") + Rep(AnyBut("\n"))
    escaped_newline = Str("\\\n")
    lineterm = Str("\n") | Eof

    lexicon = Lexicon(
        [
            (name, 'name'),
            (number, 'number'),
            (stringlit, 'string'),
            (punct | diphthong, TEXT),
            (bra, open_bracket_action),
            (ket, close_bracket_action),
            (lineterm, newline_action),
            (comment, IGNORE),
            (spaces, IGNORE),
            (escaped_newline, IGNORE),
            State('indent', [
                (indentation + Opt(comment) + lineterm, IGNORE),
                (indentation, indentation_action),
            ]),
        ], 
        debug = None,
        timings = None
    )

    def __init__(self, file, name=''):
        Scanner.__init__(self, self.lexicon, file, name)
        self.indentation_stack = [0]
        self.begin('indent')


def test9():
    run_scanner(None, "test9", scanner_class = MyScanner)
